package com.basic.ui.view

/**
 * @author Peter Liu
 * @since 2023/7/19 21:54
 *
 */

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.annotation.AttrRes
import androidx.coordinatorlayout.R
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
import com.basic.ui.utils.screenWidth

/**
 * 基于Viewpager实现的水平可滚动选择的带粘性效果的list view. 可设置选中时放大效果，
 * 默认情况下第一个pager item 会显示在屏幕最中间， 其左边没有item，第二个item 会显示在第一个的右边
 * 缺点：水平滚动不是非常流畅，后续会提供基于recycleview的版本实现
 * 注意 item 布局中设置with是没有效果的，会根据pagesize调整，故item如果有固定大小请设置到布局的子view 上
 *
 * @author: Peter Liu
 * @date: 2023/7/15
 */

class HorizontalSelectListView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null,
    @AttrRes defStyleAttr: Int = R.attr.coordinatorLayoutStyle
) : CoordinatorLayout(context, attrs, defStyleAttr) {

    val viewList: ViewPager = ViewPager(getContext())
    private var itemViewInflater: Itemflater<Any>? = null

    init {
        viewList.clipToPadding = false
        addView(viewList, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    }

    fun setItemSize(itemSize: Int) {
        val padding = (context.screenWidth - itemSize) / 2
        setViewPagerPadding(padding, 0, padding, 0)
    }

    fun setViewPagerPadding(paddingLeft: Int, paddingTop: Int, paddingRight: Int, paddingBottom: Int) {
        viewList.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
    }

    fun setItemDivider(marginPixels: Int) {
        viewList.pageMargin = marginPixels
    }

    fun <T> setItemInflater(itemViewInflater: Itemflater<T>) {
        this.itemViewInflater = itemViewInflater as Itemflater<Any>
    }

    fun <T> setItems(itemList: List<T>) {
        if (this.itemViewInflater == null) {
            throw IllegalArgumentException("You must setItemInflater first.")
        }
        setItems(itemList, this.itemViewInflater as Itemflater<T>)
    }

    fun <T> setItems(
        itemList: List<T>,
        itemViewInflater: Itemflater<T>
    ) {
        val adapter = SelectPagerAdapter(itemList, itemViewInflater)
        viewList.adapter = adapter
    }

    fun setOffscreenPageLimit(limit: Int) {
        viewList.offscreenPageLimit = limit
    }

    fun getCount(): Int? {
        return viewList.adapter?.count
    }

    fun setCurrentItem(itemIndex: Int) {
        viewList.currentItem = itemIndex
    }

    fun getCurrentItem(): Int {
        return viewList.currentItem
    }

    fun <T> getItemData(pos: Int): T? {
        return (viewList.adapter as? SelectPagerAdapter<*>)?.getItem(pos) as? T
    }

    fun getItemView(pos: Int): View? {
        return (viewList.adapter as? SelectPagerAdapter<*>)?.getItemView(pos)
    }

    fun <T> getCurrentItemData(): T? {
        return getItemData<T>(getCurrentItem())
    }

    fun addItemSelectChangeListener(listener: ItemSelectChangeListener) {
        viewList.addOnPageChangeListener(listener)
    }

    fun removeItemSelectChangeListener(listener: OnPageChangeListener) {
        viewList.removeOnPageChangeListener(listener)
    }

    fun setSelectScale(maxSelectedScale: Float) {
        addItemSelectChangeListener(PageScaleTransform(this, maxSelectedScale))
    }

    internal class SelectPagerAdapter<T>(
        private var data: List<T> = emptyList(),
        var itemViewInflater: Itemflater<T>,
    ) : PagerAdapter() {
        private val views by lazy { HashMap<Int, View>() }

        fun setItems(items: List<T>) {
            data = items
            notifyDataSetChanged()
        }

        override fun getCount(): Int {
            return data.size
        }

        fun getItem(position: Int): T {
            return data[position]
        }

        fun getItemView(position: Int): View? {
            return views[position]
        }

        override fun isViewFromObject(view: View, `object`: Any): Boolean {
            return view == `object`
        }

        override fun instantiateItem(container: ViewGroup, position: Int): View {
            val itemView = itemViewInflater.invoke(container, getItem(position), position)
            views[position] = itemView
            return itemView
        }

        override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
            container.removeView(`object` as View)
            views.remove(position)
        }
    }
}

open class ItemSelectChangeListener : OnPageChangeListener {
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {

    }

    override fun onPageSelected(position: Int) {
    }

    override fun onPageScrollStateChanged(state: Int) {
    }
}

class PageScaleTransform(
    private val horizontalListView: HorizontalSelectListView,
    maxSelectScale: Float
) : ItemSelectChangeListener() {
    private var lastPosOffset = 0f

    private val actualScale: Float = maxSelectScale - 1f

    override fun onPageScrolled(
        position: Int,
        positionOffset: Float,
        positionOffsetPixels: Int
    ) {
        val count = horizontalListView.getCount() ?: return
        val realCurrentPosition: Int
        val nextPosition: Int
        val realOffset: Float
        if (positionOffset > lastPosOffset) { //go to right
            nextPosition = position + 1
            realCurrentPosition = position
            realOffset = positionOffset
        } else {
            realCurrentPosition = position + 1
            nextPosition = position
            realOffset = 1 - positionOffset
        }

        // Avoid crash on overscroll
        if (nextPosition > count - 1
            || realCurrentPosition > count - 1
        ) {
            return
        }
        val currentView = horizontalListView.getItemView(realCurrentPosition)
        if (currentView != null) {
            currentView.scaleX = 1 + actualScale * (1f - realOffset)
            currentView.scaleY = 1 + actualScale * (1 - realOffset)
        }

        // We might be scrolling fast enough so that the next (or previous) card
        // was already destroyed or a fragment might not have been created yet
        val nextView = horizontalListView.getItemView(nextPosition)
        if (nextView != null) {
            nextView.scaleX = (1 + actualScale * (realOffset))
            nextView.scaleY = (1 + actualScale * (realOffset))
        }
        lastPosOffset = positionOffset
    }
}